﻿#region File Description
/*-----------------------------------------------------------------------------
 * Class: Camera
 *
 * loosely based on msdn fuelcell (probably no original code left)
 *
 *
 * Notes: 
 -------------------------------------------------------------------------------*/
#endregion

namespace Cubezicles
{
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Input;
    
    public interface ICamera
    {
        Vector3 Position { get; }
        Vector3 Forward { get; }
        Vector3 Up { get; }
        Matrix ViewMatrix { get; }
        Matrix ProjectionMatrix { get; }
    }

    public class Camera : GameComponent, ICamera
    {
        // Position of the camera (before zoom; Y never changes)
        private static readonly Vector3 InitialCameraPosition = new Vector3(10f, 15f, 15f); //(-5f, 15f, -5f);
        private static readonly float InitialZoom = -5f;
        private static readonly Rectangle CameraConstraints =
            new Rectangle(-50, -50, 100, 100);

        // Where the camera is in space
        private Vector3 cameraPosition = InitialCameraPosition;

        // Holds the rotation (both forward (angle down) and about the Y axis)
        private Matrix cameraRotation = Matrix.CreateRotationX(-MathHelper.PiOver4);

        // Minimum/maximum permissible zoom (TODO: find optimal values?)
        private const float MinZoom = -20.0f, MaxZoom = 5.0f;

        // Zoom represents the scale to which we move closer to or further from
        // the original camera position
        private float _zoom = InitialZoom; //0.0f;

        // Set rates in world units per second (will be adjusted based on time diffs)
        const float rotationSpeed = 1.1f, motionSpeed = 20.1f; // bumped both up .1f

        // Set field of view of the camera in radians
        const float FOV = MathHelper.PiOver4;

        // Set distance from the camera of the near and far clipping planes.
        // Caution: large values of farClip / nearClip can reduce depth precision
        const float nearClip = 1.0f, farClip = 500.0f;

        float time; 

        // Properties; implement ICamera
        #region properties

        // Get/set camera zoom (respect min/max values)
        private float zoom
        {
            get { return _zoom; }
            set { _zoom = MathHelper.Clamp(value, MinZoom, MaxZoom); }
        }

        // Get the left direction vector
        private Vector3 Left
        {
            get { return cameraRotation.Left; }
        }

        // Get the forward-direction vector (since we move parallel to the ground)
        private Vector3 forwardMotion
        {
            get
            {
                // Get the direction we're facing, make it parallel to the ground,
                // then normalize and return
                var result = Forward;
                result.Y = 0;
                result.Normalize();
                return result;
            }
        }

        // Get the camera position (adjusted for zoom)
        public Vector3 Position
        {
            get { return cameraPosition + Forward * zoom; }
        }

        // Get the vector pointing up
        public Vector3 Up
        {
            get { return cameraRotation.Up; }
        }

        // Get the camera direction as a vector
        public Vector3 Forward
        {
            get { return cameraRotation.Forward; }
        }

        // Camera position matrix
        public Matrix ViewMatrix
        {
            get
            {
                return Matrix.CreateLookAt(Position, Position + Forward, Up);
            }
        }

        // Projection matrix
        public Matrix ProjectionMatrix { get; private set; }

        #endregion

        /// <summary>
        /// Camera class constructor.
        /// </summary>
        /// <param name="g">Game object using this camera</param>
        public Camera(Game game) : base(game)
        {
            var viewport = game.GraphicsDevice.Viewport;
            float aspectRatio = (float)viewport.Width / (float)viewport.Height;

            //proj = Matrix.CreatePerspectiveFieldOfView(viewAngle, aspectRatio,
            ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(FOV, aspectRatio,
                nearClip, farClip);

            game.Services.AddService<ICamera>(this);
        }

        public void CenterCameraOn(PlaceableGameComponent pgc)
        {
            cameraPosition = new Vector3(pgc.Position.X, 15f, pgc.Position.Z);
        }

        /// <summary>
        /// Update the position and direction of the camera.
        /// </summary>
        /// 

        // Camera Control functions to be called from the interface
        public void zoomIn() 
        {
            // Zoom in
            zoom += motionSpeed * time;
        }

        public void zoomOut()
        {
            // Zoom out
            zoom -= motionSpeed * time;
        }

        public void rotateClock()
        {
            // rotate right
            cameraRotation = cameraRotation * Matrix.CreateRotationY(-rotationSpeed * time);
        }

        public void rotateCounter()
        {
            // Rotate left
            cameraRotation = cameraRotation * Matrix.CreateRotationY(rotationSpeed * time);
        }

        public override void Update(GameTime gameTime)
        {
            // Difference in time from last frame tells us how far to move
            time = (float)gameTime.ElapsedGameTime.TotalSeconds;

            var keyState = Keyboard.GetState();

            // Rotate left/right using < > keys
            if (keyState.IsKeyDown(Keys.OemComma))
            {
                rotateCounter();
            }
            else if (keyState.IsKeyDown(Keys.OemPeriod))
            {
                rotateClock();
            }

            //----- Zoom in/out using + - keys
            if (keyState.IsKeyDown(Keys.OemPlus))
            {
                zoomIn();
            }
            else if (keyState.IsKeyDown(Keys.OemMinus))
            {
                zoomOut();
            }

            //----- Use arrow keys to pan left/right/up/down
            if (keyState.IsKeyDown(Keys.Left) || keyState.IsKeyDown(Keys.A))
            {
                // Move camera left
                cameraPosition += Left * motionSpeed * time;
            }
            else if (keyState.IsKeyDown(Keys.Right) || keyState.IsKeyDown(Keys.D))
            {
                // Move camera right
                cameraPosition -= Left * motionSpeed * time;
            }

            if (keyState.IsKeyDown(Keys.Up) || keyState.IsKeyDown(Keys.W))
            {
                // Move camera forward
                cameraPosition += forwardMotion * motionSpeed * time;
            }
            else if (keyState.IsKeyDown(Keys.Down) || keyState.IsKeyDown(Keys.S))
            {
                // Move camera backward
                cameraPosition -= forwardMotion * motionSpeed * time;
            }

            cameraPosition.X = MathHelper.Clamp(cameraPosition.X, CameraConstraints.Left, CameraConstraints.Right);
            cameraPosition.Z = MathHelper.Clamp(cameraPosition.Z, CameraConstraints.Top, CameraConstraints.Bottom);
        }
    }
}
